/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.kernel;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import fr.x9c.cadmium.primitives.stdlib.Compare;
import fr.x9c.cadmium.util.IO;

/**
 * This class provides support for custom operations, that is type-dependent
 * implementations for:
 * <ul>
 *   <li>comparison;</li>
 *   <li>hash;</li>
 *   <li>[de]serialization;</li>
 *   <li>finalization.</li>
 * </ul>
 * <br/>
 * This class also provides custom implementation for <tt>int32</tt>,
 * <tt>int64</tt>, <tt>nativeint</tt>, and <tt>_chan</tt> types.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
public final class Custom {

    /** Operations for 32-bit signed integers. */
    public static final Operations INT_32_OPS = new Int32Ops();

    /** Size of 32-bit signed integers. */
    public static final int INT_32_SIZE = 4;

    /** Operations for 64-bit signed integers. */
    public static final Operations INT_64_OPS = new Int64Ops();

    /** Size of 64-bit signed integers. */
    public static final int INT_64_SIZE = 8;

    /** Operations for native signed integers. */
    public static final Operations INT_NAT_OPS = new IntNatOps();

    /** Size of native signed integers. */
    public static final int INT_NAT_SIZE = 4;

    /** Operations for channels. */
    public static final Custom.Operations CHANNEL_OPS = new ChannelOps();

    /** Size of channels. */
    public static final int CHANNEL_SIZE = 44 + Channel.BUFFER_SIZE;

    /**
     * No instance of this class.
     */
    private Custom() {
    } // end empty constructor

    /**
     * This class is a simple data structure (with public fields) storing
     * element sizes for 32-bit and 64-bit architectures.
     */
    public static final class SerializationSizes {

        /** Element size for 32-bit architecture. */
        public long size32;

        /** Element size for 64-bit architecture. */
        public long size64;

        /**
         * Constructs an instance with all fields initialized to zero.
         */
        public SerializationSizes() {
            this.size32 = 0L;
            this.size64 = 0L;
        } // end empty constructor

        /**
         * Constructs an instance from sizes for 32- and 64-bit architectures.
         * @param s32 size for 32-bit architectures
         * @param s64 size for 64-bit architectures
         */
        public SerializationSizes(final long s32, final long s64) {
            this.size32 = s32;
            this.size64 = s64;
        } // end constructor(long, long)

    } // end inner-class 'SerializationSizes'

    /**
     * This interface describes operations that a custom type may provide
     * (some operations are optional).
     */
    public interface Operations {

        /**
         * Returns the identifier of the custom type implemented by these
         * operations.
         * @return the identifier of the custom type
         */
        String getIdentifier();

        /**
         * Runs finalization operations for a value being garbage-collected.
         * @param v value to finalize - should not be <tt>null</tt>
         */
        void finalize(Value v);

        /**
         * Tests whether comparison is supported.
         * @return <tt>true</tt> if comparison is supported,
         *         <tt>false</tt> otherwise
         */
        boolean isComparable();

        /**
         * Compares two values.
         * @param v1 first value - should not be <tt>null</tt>
         * @param v2 second value - should not be <tt>null</tt>
         * @param unordered to be set if values are unordered
         * @return -1 if first value is lower than second one,
         *         +1 if first value is greater than second one,
         *         0 if values are equal,
         *         another value if values are unordered <br/>
         *         result is undefined when values are not comparable
         */
        int compare(Value v1, Value v2, AtomicBoolean unordered);

        /**
         * Tests whether hashing is supported.
         * @return <tt>true</tt> if hashing is supported,
         *         <tt>false</tt> otherwise
         */
        boolean isHashable();

        /**
         * Computes the hash of a value.
         * @param v value to hash - should not be <tt>null</tt>
         * @return hash of passed value <br/>
         *         result is undefined when values are not hashable
         */
        int hash(Value v);

        /**
         * Tests whether serialization is supported.
         * @return <tt>true</tt> if serialization is supported,
         *         <tt>false</tt> otherwise
         */
        boolean isSerializable();

        /**
         * Serializes a 'custom' block.
         * @param out data destination - should not be <tt>null</tt>
         * @param v value to serialize - should not be <tt>null</tt>
         * @return the sizes of serialized value for 32-bit and 64-bit
         *         architectures <br/>
         *         result is undefined when values are not serializable
         * @throws IOException if an I/O error occurs
         */
        SerializationSizes serialize(DataOutput out, Value v)
            throws IOException;

        /**
         * Deserializes a 'custom' block.
         * @param in data source - should not be <tt>null</tt>
         * @param dst where to put deserialized block
         *            - should not be <tt>null</tt>
         * @return the size of the block in read bytes <br/>
         *         result is undefined when values are not serializable
         * @throws IOException if an I/O error occurs
         * @throws Fail.Exception if deserialization fails due to data source
         *                        content
         * @throws Fatal.Exception if deserialization fails due to platform
         *                         constraints
         */
        long deserialize(DataInput in, Block dst)
            throws IOException, Fail.Exception, Fatal.Exception;

    } // end inner-interface 'Operations'

    /**
     * This class implements the default behaviour for custom operations.
     */
    public static class AbstractOperations implements Custom.Operations {

        /** Identifier of custom type. */
        private final String id;

        /** Whether the type is comparable. */
        private final boolean comparable;

        /** Whether the type is hashable. */
        private final boolean hashable;

        /** Whether the type is serializable. */
        private final boolean serializable;

        /**
         * Constructs operations from identifier and characteristics.
         * @param opID identifier of custom type - should not be <tt>null</tt>
         * @param comp whether the type is comparable
         * @param hash whether the type is hashable
         * @param serial whether the type is serializable
         */
        public AbstractOperations(final String opID,
                                  final boolean comp,
                                  final boolean hash,
                                  final boolean serial) {
            assert opID != null : "null opID";
            this.id = opID;
            this.comparable = comp;
            this.hashable = hash;
            this.serializable = serial;
        } // end constructor(String, boolean, boolean, boolean)

        /**
         * {@inheritDoc}
         */
        @Override
        public final String getIdentifier() {
            return this.id;
        } // end method 'getIdentifier()'

        /**
         * {@inheritDoc}
         */
        @Override
        public void finalize(final Value v) {
            assert v != null : "null v";
        } // end method 'finalize(Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean isComparable() {
            return this.comparable;
        } // end method 'isComparable()'

        /**
         * {@inheritDoc}
         */
        @Override
        public int compare(final Value v1,
                           final Value v2,
                           final AtomicBoolean unordered) {
            assert v1 != null : "null v1";
            assert v2 != null : "null v2";
            assert false : "unsupported operation";
            return 0;
        } // end method 'compare(Value, Value, AtomicBoolean)'

        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean isHashable() {
            return this.hashable;
        } // end method 'isHashable()'

        /**
         * {@inheritDoc}
         */
        @Override
        public int hash(final Value v) {
            assert v != null : "null v";
            assert false : "unsupported operation";
            return 0;
        } // end method 'hash(Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean isSerializable() {
            return this.serializable;
        } // end method 'isSerializable()'

        /**
         * {@inheritDoc}
         */
        @Override
        public SerializationSizes serialize(final DataOutput out, final Value v)
            throws IOException {
            assert out != null : "null out";
            assert v != null : "null v";
            assert false : "unsupported operation";
            return null;
        } // end method 'serialize(DataOutput, Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public long deserialize(final DataInput in, final Block dst)
            throws IOException, Fail.Exception, Fatal.Exception {
            assert in != null : "null in";
            assert dst != null : "null dst";
            assert false : "unsupported operation";
            return 0;
        } // end method 'deserialize(DataInput, Block)'

    } // end inner-class 'AbstractOperations'

    /**
     * Implementation of signed 32-bit integers.
     */
    private static final class Int32Ops extends Custom.AbstractOperations {

        /**
         * Constructs an instance.
         */
        private Int32Ops() {
            super("_i", true, true, true);
        } // end empty constructor

        /**
         * {@inheritDoc}
         */
        @Override
        public int compare(final Value v1,
                           final Value v2,
                           final AtomicBoolean unordered) {
            assert v1 != null : "null v1";
            assert v2 != null : "null v2";
            final int i1 = v1.asBlock().asInt32();
            final int i2 = v2.asBlock().asInt32();
            if (i1 < i2) {
                return Compare.LESS;
            } else if (i1 > i2) {
                return Compare.GREATER;
            } else {
                return Compare.EQUAL;
            } // end if/elsif/else
        } // end method 'compare(Value, Value, AtomicBoolean)'

        /**
         * {@inheritDoc}
         */
        @Override
        public int hash(final Value v) {
            assert v != null : "null v";
            return v.asBlock().asInt32();
        } // end method 'hash(Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public SerializationSizes serialize(final DataOutput out,
                                            final Value v)
            throws IOException {
            assert out != null : "null out";
            assert v != null : "null v";
            IO.write32s(out, v.asBlock().asInt32());
            return new SerializationSizes(Custom.INT_32_SIZE, Custom.INT_32_SIZE);
       } // end method 'serialize(DataOutput, Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public long deserialize(final DataInput in, final Block dst)
            throws IOException {
            assert in != null : "null in";
            assert dst != null : "null dst";
            dst.setInt32(IO.read32s(in));
            return Custom.INT_32_SIZE;
        } // end method 'deserialize(DataInput, Block)'

    } // end inner-class 'Int32Ops'

    /**
     * Implementation of signed 64-bit integers.
     */
    private static final class Int64Ops extends Custom.AbstractOperations {

        /**
         * Constructs an instance.
         */
        private Int64Ops() {
            super("_j", true, true, true);
        } // end empty constructor

        /**
         * {@inheritDoc}
         */
        @Override
        public int compare(final Value v1,
                           final Value v2,
                           final AtomicBoolean unordered) {
            assert v1 != null : "null v1";
            assert v2 != null : "null v2";
            final long l1 = v1.asBlock().asInt64();
            final long l2 = v2.asBlock().asInt64();
            if (l1 < l2) {
                return Compare.LESS;
            } else if (l1 > l2) {
                return Compare.GREATER;
            } else {
                return Compare.EQUAL;
            } // end if/elsif/else
        } // end method 'compare(Value, Value, AtomicBoolean)'

        /**
         * {@inheritDoc}
         */
        @Override
        public int hash(final Value v) {
            assert v != null : "null v";
            return (int) (v.asBlock().asInt64() & 0xFFFFFFFFL);
        } // end method 'hash(Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public SerializationSizes serialize(final DataOutput out,
                                            final Value v)
            throws IOException {
            assert out != null : "null out";
            assert v != null : "null v";
            IO.write64s(out, v.asBlock().asInt64());
            return new SerializationSizes(Custom.INT_64_SIZE, Custom.INT_64_SIZE);
       } // end method 'serialize(DataOutput, Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public long deserialize(final DataInput in, final Block dst)
            throws IOException {
            assert in != null : "null in";
            assert dst != null : "null dst";
            final long l = IO.read64s(in);
            dst.setInt64(l);
            return Custom.INT_64_SIZE;
        } // end method 'deserialize(DataInput, Block)'

    } // end inner-class 'Int64Ops'

    /**
     * Implementation of signed native integers.
     */
    private static final class IntNatOps extends Custom.AbstractOperations {

        /**
         * Constructs an instance.
         */
        private IntNatOps() {
            super("_n", true, true, true);
        } // end empty constructor

        /**
         * {@inheritDoc}
         */
        @Override
        public int compare(final Value v1,
                           final Value v2,
                           final AtomicBoolean unordered) {
            assert v1 != null : "null v1";
            assert v2 != null : "null v2";
            final int i1 = v1.asBlock().asNativeInt();
            final int i2 = v2.asBlock().asNativeInt();
            if (i1 < i2) {
                return Compare.LESS;
            } else if (i1 > i2) {
                return Compare.GREATER;
            } else {
                return Compare.EQUAL;
            } // end if/elsif/else
        } // end method 'compare(Value, Value, AtomicBoolean)'

        /**
         * {@inheritDoc}
         */
        @Override
        public int hash(final Value v) {
            assert v != null : "null v";
            return v.asBlock().asNativeInt();
        } // end method 'hash(Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public SerializationSizes serialize(final DataOutput out,
                                            final Value v)
            throws IOException {
            assert out != null : "null out";
            assert v != null : "null v";
            IO.write8u(out, 0x01);
            IO.write32s(out, v.asBlock().asNativeInt());
            return new SerializationSizes(Custom.INT_NAT_SIZE, Custom.INT_64_SIZE);
       } // end method 'serialize(DataOutput, Value)'

        /**
         * {@inheritDoc}
         */
        @Override
        public long deserialize(final DataInput in, final Block dst)
            throws IOException, Fail.Exception {
            assert in != null : "null in";
            assert dst != null : "null dst";
            switch (IO.read8u(in)) {
            case 0x01:
                dst.setNativeInt(IO.read32s(in));
                break;
            case 0x02:
                Fail.failWith("input_value: native integer value too large");
                break;
            default:
                Fail.failWith("input_value: ill-formed native integer");
                break;
            } // end switch
            return Custom.INT_NAT_SIZE;
        } // end method 'deserialize(DataInput, Block)'

    } // end inner-class 'IntNatOps'

    /**
     * Implementation of channels.
     */
    private static final class ChannelOps extends Custom.AbstractOperations {

        /**
         * Constructs an instance.
         */
        private ChannelOps() {
            super("_chan", false, false, false);
        } // end empty constructor

    } // end inner-class 'ChannelOps'

    /**
     * Compares two values using their magic numbers.
     * @param v1 first value - should not be <tt>null</tt>
     * @param v2 second value - should not be <tt>null</tt>
     * @return -1 if first value is lower than second one,
     *         +1 if first value is greater than second one,
     *         0 if values are equal
     */
    public static int compareUsingMagicNumbers(final Value v1,
                                               final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        final long mn1 = v1.asBlock().getMagicNumber();
        final long mn2 = v2.asBlock().getMagicNumber();
        return mn1 == mn2
            ? Compare.EQUAL
            : mn1 < mn2 ? Compare.LESS : Compare.GREATER;
    } // end method 'compareUsingMagicNumbers(Value, Value)'

} // end class 'Custom'
